// Cz 链接器管理
// 支持 Mold、LLD、GNU ld

const std = @import("std");

pub const LinkerType = enum {
    mold,
    lld,
    gnu_ld,
    auto,  // 自动选择
};

pub const Linker = struct {
    allocator: std.mem.Allocator,
    linker_type: LinkerType,
    linker_path: []const u8,
    
    pub fn init(allocator: std.mem.Allocator, preferred: LinkerType) !Linker {
        const linker_type = if (preferred == .auto) 
            try detectBestLinker(allocator)
        else 
            preferred;
        
        const linker_path = try findLinker(allocator, linker_type);
        
        return .{
            .allocator = allocator,
            .linker_type = linker_type,
            .linker_path = linker_path,
        };
    }
    
    pub fn deinit(self: *Linker) void {
        self.allocator.free(self.linker_path);
    }
    
    /// 链接目标文件
    pub fn link(
        self: *Linker,
        object_files: []const []const u8,
        output_path: []const u8,
        options: LinkOptions,
    ) !void {
        var args = std.ArrayList([]const u8).init(self.allocator);
        defer args.deinit();
        
        // 根据链接器类型构建参数
        switch (self.linker_type) {
            .mold => {
                try args.appendSlice(&[_][]const u8{
                    self.linker_path,
                    "-run",
                    "clang",  // Mold 通常通过编译器调用
                });
                try args.appendSlice(object_files);
                try args.appendSlice(&[_][]const u8{
                    "-o", output_path,
                });
                
                if (options.lto) {
                    try args.append("-flto");
                }
                
                if (options.threads > 0) {
                    const threads_arg = try std.fmt.allocPrint(
                        self.allocator,
                        "--threads={d}",
                        .{options.threads}
                    );
                    defer self.allocator.free(threads_arg);
                    try args.append(threads_arg);
                }
            },
            .lld => {
                try args.appendSlice(&[_][]const u8{
                    "clang",
                    "-fuse-ld=lld",
                });
                try args.appendSlice(object_files);
                try args.appendSlice(&[_][]const u8{
                    "-o", output_path,
                });
                
                if (options.lto) {
                    try args.append("-flto");
                }
            },
            .gnu_ld => {
                try args.append(self.linker_path);
                try args.appendSlice(object_files);
                try args.appendSlice(&[_][]const u8{
                    "-o", output_path,
                });
            },
            .auto => unreachable,
        }
        
        std.debug.print("Linking with {s}...\n", .{@tagName(self.linker_type)});
        
        var child = std.ChildProcess.init(args.items, self.allocator);
        const result = try child.spawnAndWait();
        
        if (result != .Exited or result.Exited != 0) {
            return error.LinkingFailed;
        }
    }
    
    /// 获取链接器信息
    pub fn getInfo(self: *Linker) !LinkerInfo {
        var args = std.ArrayList([]const u8).init(self.allocator);
        defer args.deinit();
        
        try args.appendSlice(&[_][]const u8{
            self.linker_path,
            "--version",
        });
        
        var child = std.ChildProcess.init(args.items, self.allocator);
        child.stdout_behavior = .Pipe;
        
        try child.spawn();
        
        const stdout = child.stdout.?.reader();
        var buf: [1024]u8 = undefined;
        const version_line = try stdout.readUntilDelimiterOrEof(&buf, '\n');
        
        _ = try child.wait();
        
        return LinkerInfo{
            .name = @tagName(self.linker_type),
            .version = if (version_line) |line| 
                try self.allocator.dupe(u8, line)
            else 
                try self.allocator.dupe(u8, "unknown"),
            .path = try self.allocator.dupe(u8, self.linker_path),
        };
    }
};

pub const LinkOptions = struct {
    lto: bool = false,
    threads: u32 = 0,  // 0 = 自动
    strip: bool = false,
    debug: bool = false,
};

pub const LinkerInfo = struct {
    name: []const u8,
    version: []const u8,
    path: []const u8,
    
    pub fn deinit(self: *LinkerInfo, allocator: std.mem.Allocator) void {
        allocator.free(self.version);
        allocator.free(self.path);
    }
};

/// 自动检测最佳链接器
fn detectBestLinker(allocator: std.mem.Allocator) !LinkerType {
    // 优先级：Mold > LLD > GNU ld
    
    // 尝试 Mold
    if (findLinker(allocator, .mold)) |path| {
        allocator.free(path);
        return .mold;
    } else |_| {}
    
    // 尝试 LLD
    if (findLinker(allocator, .lld)) |path| {
        allocator.free(path);
        return .lld;
    } else |_| {}
    
    // 回退到 GNU ld
    return .gnu_ld;
}

/// 查找链接器
fn findLinker(allocator: std.mem.Allocator, linker_type: LinkerType) ![]const u8 {
    const paths = switch (linker_type) {
        .mold => &[_][]const u8{
            "mold",
            "/usr/bin/mold",
            "/usr/local/bin/mold",
        },
        .lld => &[_][]const u8{
            "lld",
            "ld.lld",
            "/usr/bin/ld.lld",
            "/usr/local/bin/ld.lld",
        },
        .gnu_ld => &[_][]const u8{
            "ld",
            "/usr/bin/ld",
            "/usr/local/bin/ld",
        },
        .auto => unreachable,
    };
    
    for (paths) |path| {
        var child = std.ChildProcess.init(&[_][]const u8{path, "--version"}, allocator);
        const result = child.spawnAndWait() catch continue;
        
        if (result == .Exited and result.Exited == 0) {
            return try allocator.dupe(u8, path);
        }
    }
    
    return error.LinkerNotFound;
}

/// 比较链接器性能
pub fn benchmarkLinkers(allocator: std.mem.Allocator) !void {
    std.debug.print("=== Linker Benchmark ===\n\n", .{});
    
    const linker_types = [_]LinkerType{ .mold, .lld, .gnu_ld };
    
    for (linker_types) |linker_type| {
        var linker = Linker.init(allocator, linker_type) catch {
            std.debug.print("{s}: Not available\n", .{@tagName(linker_type)});
            continue;
        };
        defer linker.deinit();
        
        const info = try linker.getInfo();
        defer info.deinit(allocator);
        
        std.debug.print("{s}:\n", .{info.name});
        std.debug.print("  Version: {s}\n", .{info.version});
        std.debug.print("  Path: {s}\n", .{info.path});
        std.debug.print("\n", .{});
    }
}
